/**
 * @Author: liuzhan (lz@ifreedom.top)
 * @Description: auth
 * @Version: 1.0.0
 * @Date: 2023/12/4 21:39
 */

package context

import (
	"code.gitea.io/gitea/models/auth"
	"code.gitea.io/gitea/models/db"
	user_model "code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/modules/base"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/session"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/util"
	"code.gitea.io/gitea/modules/web/middleware"
	"code.gitea.io/gitea/routers/utils"
	auth_service "code.gitea.io/gitea/services/auth"
	"code.gitea.io/gitea/services/auth/sw_auth"
	"fmt"
	"net/http"
)

func SwAuth(ctx *APIContext, resp http.ResponseWriter, req *http.Request) (user *user_model.User) {
	var (
		token     *http.Cookie
		tokenInfo *sw_auth.Token
		err       error
	)
	// 提取token
	token, err = req.Cookie("Admin-Token-swgw")
	if err != nil {
		ctx.ServerError("提取token", err)
		return
	}
	// 验证token
	tokenInfo, err = sw_auth.ParseToken(sw_auth.SECRET, token.Value)
	if err != nil {
		ctx.ServerError("验证token", err)
		return
	}

	// 验证用户
	user, err = user_model.GetUserByUID(tokenInfo.Uid)
	if err != nil {
		ctx.ServerError("验证用户", err)
		return
	}

	if user == nil {
		pwd, _ := util.RandomString(18)
		user = &user_model.User{
			Uid:     tokenInfo.Uid,
			Name:    tokenInfo.UserName,
			Email:   tokenInfo.Uid + "@sw.com",
			Passwd:  pwd,
			IsAdmin: false,
		}
		overwriteDefault := &user_model.CreateUserOverwriteOptions{
			IsRestricted: util.OptionalBoolFalse,
			IsActive:     util.OptionalBoolTrue,
		}

		if err = user_model.CreateUser(user, overwriteDefault); err != nil {
			if !user_model.IsErrUserAlreadyExist(err) {
				setting.InstallLock = false
				ctx.Data["Err_AdminName"] = true
				ctx.Data["Err_AdminEmail"] = true
				ctx.ServerError("创建用户失败", err)
				return
			}
			user, _ = user_model.GetUserByName(user.Name)
		}

		u, source, err := auth_service.UserSignIn(user.Email, pwd)
		if err != nil {
			if user_model.IsErrUserNotExist(err) || user_model.IsErrEmailAddressNotExist(err) {
				ctx.ServerError(ctx.Tr("user.Email_password_incorrect"), err)
				log.Info("Failed authentication attempt for %s from %s: %v", user.Email, ctx.RemoteAddr(), err)
			} else if user_model.IsErrEmailAlreadyUsed(err) {
				ctx.ServerError(ctx.Tr("form.email_been_used"), err)
				log.Info("Failed authentication attempt for %s from %s: %v", user.Email, ctx.RemoteAddr(), err)
			} else if user_model.IsErrUserProhibitLogin(err) {
				log.Info("Failed authentication attempt for %s from %s: %v", user.Email, ctx.RemoteAddr(), err)
				ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
				ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
			} else if user_model.IsErrUserInactive(err) {
				if setting.Service.RegisterEmailConfirm {
					ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
					ctx.ServerError(ctx.Tr("auth.active_your_account"), err)
				} else {
					log.Info("Failed authentication attempt for %s from %s: %v", user.Email, ctx.RemoteAddr(), err)
					ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
					ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
				}
			} else {
				ctx.ServerError("UserSignIn", err)
			}
			return
		}

		// Now handle 2FA:

		// First of all if the source can skip local two fa we're done
		if skipper, ok := source.Cfg.(auth_service.LocalTwoFASkipper); ok && skipper.IsSkipLocalTwoFA() {
			handleSignIn(ctx, u, true)
			return
		}

		// If this user is enrolled in 2FA TOTP, we can't sign the user in just yet.
		// Instead, redirect them to the 2FA authentication page.
		hasTOTPtwofa, err := auth.HasTwoFactorByUID(u.ID)
		if err != nil {
			ctx.ServerError("UserSignIn", err)
			return
		}

		// Check if the user has webauthn registration
		hasWebAuthnTwofa, err := auth.HasWebAuthnRegistrationsByUID(u.ID)
		if err != nil {
			ctx.ServerError("UserSignIn", err)
			return
		}

		if !hasTOTPtwofa && !hasWebAuthnTwofa {
			// No two factor auth configured we can sign in the user
			handleSignIn(ctx, u, true)
			return
		}

		if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil {
			ctx.ServerError("UserSignIn: Unable to set regenerate session", err)
			return
		}

		// User will need to use 2FA TOTP or WebAuthn, save data
		if err := ctx.Session.Set("twofaUid", u.ID); err != nil {
			ctx.ServerError("UserSignIn: Unable to set twofaUid in session", err)
			return
		}

		if err := ctx.Session.Set("twofaRemember", true); err != nil {
			ctx.ServerError("UserSignIn: Unable to set twofaRemember in session", err)
			return
		}

		if hasTOTPtwofa {
			// User will need to use U2F, save data
			if err := ctx.Session.Set("totpEnrolled", u.ID); err != nil {
				ctx.ServerError("UserSignIn: Unable to set WebAuthn Enrolled in session", err)
				return
			}
		}

		if err := ctx.Session.Release(); err != nil {
			ctx.ServerError("UserSignIn: Unable to save session", err)
			return
		}

		// If we have U2F redirect there first
		if hasWebAuthnTwofa {
			ctx.Redirect(setting.AppSubURL + "/user/webauthn")
			return
		}

		// Fallback to 2FA
		ctx.Redirect(setting.AppSubURL + "/user/two_factor")

		return
	}

	ctx.Redirect(setting.AppSubURL + "/")

	return

}

// This handles the final part of the sign-in process of the user.
func handleSignIn(ctx *APIContext, u *user_model.User, remember bool) {
	redirect := handleSignInFull(ctx, u, remember, true)
	if ctx.Written() {
		return
	}
	ctx.Redirect(redirect)
}

func handleSignInFull(ctx *APIContext, u *user_model.User, remember bool, obeyRedirect bool) string {
	if remember {
		days := 86400 * setting.LogInRememberDays
		ctx.SetCookie(setting.CookieUserName, u.Name, days)
		ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd),
			setting.CookieRememberName, u.Name, days)
	}

	if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil {
		ctx.ServerError("RegenerateSession", err)
		return setting.AppSubURL + "/"
	}

	// Delete the openid, 2fa and linkaccount data
	_ = ctx.Session.Delete("openid_verified_uri")
	_ = ctx.Session.Delete("openid_signin_remember")
	_ = ctx.Session.Delete("openid_determined_email")
	_ = ctx.Session.Delete("openid_determined_username")
	_ = ctx.Session.Delete("twofaUid")
	_ = ctx.Session.Delete("twofaRemember")
	_ = ctx.Session.Delete("u2fChallenge")
	_ = ctx.Session.Delete("linkAccount")
	if err := ctx.Session.Set("uid", u.ID); err != nil {
		log.Error("Error setting uid %d in session: %v", u.ID, err)
	}
	if err := ctx.Session.Set("uname", u.Name); err != nil {
		log.Error("Error setting uname %s session: %v", u.Name, err)
	}
	if err := ctx.Session.Release(); err != nil {
		log.Error("Unable to store session: %v", err)
	}

	// Language setting of the user overwrites the one previously set
	// If the user does not have a locale set, we save the current one.
	if len(u.Language) == 0 {
		u.Language = ctx.Locale.Language()
		if err := user_model.UpdateUserCols(db.DefaultContext, u, "language"); err != nil {
			ctx.ServerError("UpdateUserCols Language", fmt.Errorf("Error updating user language [user: %d, locale: %s]", u.ID, u.Language))
			return setting.AppSubURL + "/"
		}
	}

	middleware.SetLocaleCookie(ctx.Resp, u.Language, 0)

	if ctx.Locale.Language() != u.Language {
		ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
	}

	// Clear whatever CSRF has right now, force to generate a new one
	middleware.DeleteCSRFCookie(ctx.Resp)

	// Register last login
	u.SetLastLogin()
	if err := user_model.UpdateUserCols(db.DefaultContext, u, "last_login_unix"); err != nil {
		ctx.ServerError("UpdateUserCols", err)
		return setting.AppSubURL + "/"
	}

	if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) {
		middleware.DeleteRedirectToCookie(ctx.Resp)
		if obeyRedirect {
			ctx.RedirectToFirst(redirectTo)
		}
		return redirectTo
	}

	if obeyRedirect {
		ctx.Redirect(setting.AppSubURL + "/")
	}
	return setting.AppSubURL + "/"
}
